package cn.gyn.gynrpc.core.provider;

import cn.gyn.gynrpc.core.api.RpcContext;
import cn.gyn.gynrpc.core.api.RpcException;
import cn.gyn.gynrpc.core.api.RpcRequest;
import cn.gyn.gynrpc.core.api.RpcResponse;
import cn.gyn.gynrpc.core.config.ProviderProperties;
import cn.gyn.gynrpc.core.governance.SlidingTimeWindow;
import cn.gyn.gynrpc.core.meta.ProviderMeta;
import cn.gyn.gynrpc.core.util.TypeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.MultiValueMap;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static cn.gyn.gynrpc.core.api.RpcException.ExceedLimitEx;

@Slf4j
public class ProviderInvoker {
    private MultiValueMap<String, ProviderMeta> skeleton;

    private final int trafficControl;
    final Map<String, SlidingTimeWindow> windows = new HashMap<>();

    //final Map<String, String> metas;

    final ProviderProperties providerProperties;

    //TC方法2
    /*ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    int a = 0;*/

    /**
     * 从idea左上角点开菜单-code-generate-constructor,生成构造函数
     * @param providerBootstrap
     */
    public ProviderInvoker(ProviderBootstrap providerBootstrap) {
        this.skeleton = providerBootstrap.getSkeleton();

        //this.metas = providerBootstrap.getProviderProperties().getMetas();//这样赋值即使APOLLO改后刷新了ProviderProperties，但metas本身的hashcode没被刷新，所以获取不到最新的值。因此分开写
        this.providerProperties = providerBootstrap.getProviderProperties();
        //限流：30s滑动时间窗口内允许访问25次（这里provider端pom里的流量控制traffic-control:tc为准，为空给默认的20），超过就抛异常不做真实调用了
        this.trafficControl = Integer.parseInt(providerProperties.getMetas().getOrDefault("tc", "20"));
//        service.scheduleWithFixedDelay(() -> {
//            //每30秒重置回限流上限值，在实际调用中加上并发控制进行a--，为0就抛异常
//            a = 20;
//        }, 30, 30, TimeUnit.SECONDS);
    }

    /**
     * 实现RPC的调用
     * 为啥要给返回参数加个<Object>?
     * 可能的解释？使用泛型的 responseClass<Object> 可以明确指定返回类型为 Object，即表示可以接收任何类型的对象作为返回值。使用泛型可以提供类型安全性，避免了在使用时进行类型转换或处理的麻烦。
     * @param request
     * @return
     */
    public RpcResponse<Object> Invoke(RpcRequest request) {
        log.debug(" ===> ProviderInvoker.invoke(request:{})", request);
        if (!request.getParams().isEmpty()) {
            //把params里的map传到上下文里的ContextParameters map里去
            request.getParams().forEach(RpcContext::setContextParameter);
        }
        RpcResponse<Object> rpcResponse = new RpcResponse();
        String service = request.getService();
        //限流：给每个服务加个滑动时间窗口，trafficControl数目可以根据服务不同分别定义，改为map，对应service和不同流控制
        //不同服务对系统性能影响和资源耗用时不一样的，所以要做区分，流控逻辑是保护系统资源
        synchronized (windows) {
            SlidingTimeWindow window = windows.computeIfAbsent(service, k -> new SlidingTimeWindow());
            if (window.calcSum() >= trafficControl) {
                System.out.println(window);
                throw new RpcException("service " + service + " invoked in 30s/[" +
                        window.getSum() + "] larger than tpsLimit = " + trafficControl, ExceedLimitEx);
            }
            //正常调用服务才会做一次记录
            window.record(System.currentTimeMillis());
            log.debug("service {} in window with {}", service, window.getSum());
        }

        //通过request里的全限定名，获取到skeleton对应的服务端的类
        List<ProviderMeta> providerMetas = skeleton.get(service);
        try {
            ProviderMeta providerMeta = findProviderMeta(providerMetas, request.getMethodSign());
            //再通过反射获取到类里的方法

            //Method method = bean.getClass().getMethod(request.getMethod());//这个方法获取不到方法，因此暂时用了下面这种方法偷懒，为什么？
            Method method = providerMeta.getMethod();
            //因为request里args会自动进行一些类型转换，导致方法调用失败，这里对args进行处理
            Object[] args = processArgs(request.getArgs(), method.getParameterTypes(), method.getGenericParameterTypes());
            //传入具体类和请求方法的参数，反射实现方法
            Object result = method.invoke(providerMeta.getServiceImpl(), args);
            rpcResponse.setStatus(true);
            rpcResponse.setData(result);
        } catch (InvocationTargetException e) {
            //希望客户端只看到message里的异常
            rpcResponse.setException(new RpcException(e.getTargetException().getMessage()));
        } catch (IllegalAccessException e) {
            rpcResponse.setException(new RpcException(e.getMessage()));
        } finally {
            RpcContext.ContextParameters.get().clear(); // 防止内存泄露和上下文污染
        }
        return rpcResponse;

    }

    private Object[] processArgs(Object[] args, Class<?>[] parameterTypes, Type[] genericParameterTypes) {
        if (args == null || args.length == 0) return args;
        Object[] realArgs = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            realArgs[i] = TypeUtils.castGeneric(args[i], parameterTypes[i], genericParameterTypes[i]);
        }
        return realArgs;
    }

    private ProviderMeta findProviderMeta(List<ProviderMeta> providerMetas, String methodSign) {
        Optional<ProviderMeta> optinal = providerMetas.stream().filter(x -> x.getMethodSign().equals(methodSign)).findFirst();
        return optinal.orElse(null);
    }
}
